
// HighSpeedCaptureDlg.cpp : implementation file
//

#include "stdafx.h"
#include "HighSpeedCapture.h"
#include "HighSpeedCaptureDlg.h"
#include "afxdialogex.h"

#include <tisudshl.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_UPDATE_STATS_UI (WM_USER + 1)

// CHighSpeedCaptureDlg dialog

CHighSpeedCaptureDlg::CHighSpeedCaptureDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CHighSpeedCaptureDlg::IDD, pParent)
	, m_destinationFolder( _T( "" ) )
	, m_bufferMemoryMiB( 0 )
	, m_sinkStats()
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CHighSpeedCaptureDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange( pDX );
	DDX_Text( pDX, IDC_EDIT_DESTINATION, m_destinationFolder );
	DDX_Control( pDX, IDC_START_STOP, m_btnStartStop );
	DDX_Control( pDX, IDC_BUFFER_MAXIMIZE, m_btnBufferMaximize );
	DDX_Control( pDX, IDC_DESTINATION_BROWSE, m_btnDestinationBrowse );
	DDX_Control( pDX, IDC_PROGRESS_FREE, m_progressFreeBuffers );
	DDX_Control( pDX, IDC_PROGRESS_FILLED, m_progressFilledBuffers );
	DDX_Control( pDX, IDC_LABEL_FREE, m_lblFreeBuffers );
	DDX_Control( pDX, IDC_LABEL_FILLED, m_lblFilledBuffers );
	DDX_Text( pDX, IDC_EDIT_BUFFER_MEMORY, m_bufferMemoryMiB );
	DDX_Control( pDX, IDC_LABEL_CAPTUREINFO, m_lblCaptureInfo );
}

BEGIN_MESSAGE_MAP(CHighSpeedCaptureDlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_GETMINMAXINFO()
	ON_BN_CLICKED( IDC_DESTINATION_BROWSE, &CHighSpeedCaptureDlg::OnBnClickedDestinationBrowse )
	ON_WM_SIZE()
	ON_BN_CLICKED( IDC_DEVICE, &CHighSpeedCaptureDlg::OnBnClickedDevice )
	ON_BN_CLICKED( IDC_START_STOP, &CHighSpeedCaptureDlg::OnBnClickedStartStop )
	ON_WM_CLOSE()
	ON_MESSAGE( WM_UPDATE_STATS_UI, &CHighSpeedCaptureDlg::OnUpdateStatsUI )
	ON_EN_KILLFOCUS( IDC_EDIT_BUFFER_MEMORY, &CHighSpeedCaptureDlg::OnKillfocusEditBufferMemory )
	ON_BN_CLICKED( IDC_BUFFER_MAXIMIZE, &CHighSpeedCaptureDlg::OnBnClickedBufferMaximize )
	ON_WM_TIMER()
END_MESSAGE_MAP()


// CHighSpeedCaptureDlg message handlers

BOOL CHighSpeedCaptureDlg::OnInitDialog()
{
	// Initialize destination folder
	CString defaultDestinationFolder = TEXT("");
	PWSTR picturesPath = NULL;
	if( SUCCEEDED( SHGetKnownFolderPath( FOLDERID_Pictures, 0, NULL, &picturesPath ) ) )
	{
		defaultDestinationFolder = picturesPath;
		defaultDestinationFolder += TEXT( "\\HighSpeedCapture" );

		::CoTaskMemFree( picturesPath );
	}

	m_destinationFolder = theApp.GetString( TEXT( "m_destinationFolder" ), defaultDestinationFolder );

	m_bufferMemoryMiB = 1024;
	

	CDialogEx::OnInitDialog();	

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// Query initial window size to set lower size limit
	CRect rc;
	GetWindowRect( rc );
	m_minSize = rc.Size();

	m_Grabber.setHWND( GetDlgItem( IDC_VIDEO )->m_hWnd );

	m_Grabber.loadDeviceStateFromFile( "device.xml" );

	OnBnClickedDevice();

	if( !m_Grabber.isDevValid() )
	{
		DestroyWindow();
	}

	SetTimer( 0, 250, 0 );

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CHighSpeedCaptureDlg::OnGetMinMaxInfo( MINMAXINFO* lpMMI )
{
	lpMMI->ptMinTrackSize.x = m_minSize.cx;
	lpMMI->ptMinTrackSize.y = m_minSize.cy;

	CDialogEx::OnGetMinMaxInfo( lpMMI );
}

void CHighSpeedCaptureDlg::OnBnClickedDestinationBrowse()
{
	theApp.GetShellManager()->BrowseForFolder( m_destinationFolder, this, m_destinationFolder, TEXT("Select destination folder"), BIF_VALIDATE/*|BIF_USENEWUI*/ );

	theApp.WriteString( TEXT("m_destinationFolder"), m_destinationFolder );

	UpdateData( FALSE );
}


void CHighSpeedCaptureDlg::OnSize( UINT nType, int cx, int cy )
{
	CDialogEx::OnSize( nType, cx, cy );

	ResizeLiveVideo();
}

static void FitLiveVideoIntoWindow( DShowLib::Grabber& grabber, HWND hWndVideo )
{
	RECT rc;
	::GetClientRect( hWndVideo, &rc );
	int windowWidth = rc.right - rc.left;
	int windowHeight = rc.bottom - rc.top;

	SIZE liveVideoSize;
	grabber.getVideoDataDimension( liveVideoSize );

	float liveVideoRatio = liveVideoSize.cx / (float)liveVideoSize.cy;
	float windowRatio = windowWidth / (float)windowHeight;

	grabber.setDefaultWindowPosition( false );

	if( liveVideoRatio > windowRatio )
	{
		grabber.setWindowSize( windowWidth, (long)(windowWidth / liveVideoRatio) );
	}
	else
	{
		grabber.setWindowSize( (long)(windowHeight * liveVideoRatio), windowHeight );
	}
}

void CHighSpeedCaptureDlg::ResizeLiveVideo()
{
	CWnd* wndVideo = GetDlgItem( IDC_VIDEO );
	if( wndVideo )
	{
		FitLiveVideoIntoWindow( m_Grabber, *wndVideo );
	}
}

static GUID SelectMatchingSinkFormat( GUID format )
{
	if( format == DShowLib::MEDIASUBTYPE_Y800 )
		return DShowLib::MEDIASUBTYPE_Y800;
	if( format == DShowLib::MEDIASUBTYPE_Y16 )
		return DShowLib::MEDIASUBTYPE_Y16;
	if( format == MEDIASUBTYPE_RGB32 )
		return MEDIASUBTYPE_RGB32;
	if( format == DShowLib::MEDIASUBTYPE_RGB64 )
		return DShowLib::MEDIASUBTYPE_RGB64;
	return MEDIASUBTYPE_RGB24;
}

void CHighSpeedCaptureDlg::OnBnClickedDevice()
{
	m_Grabber.stopLive();
	m_Grabber.showDevicePage( m_hWnd );

	if( m_Grabber.isDevValid() )
	{
		m_Grabber.saveDeviceStateToFile( "device.xml" );

//<<sinksetup
		GUID sinkFormat = SelectMatchingSinkFormat( m_Grabber.getVideoFormat().getSubtype() );
		m_pSink = DShowLib::FrameQueueSink::create( *this, sinkFormat );
		m_pSink->setSinkMode( DShowLib::GrabberSinkType::ePAUSE );
		m_Grabber.setSinkType( m_pSink );

		m_Grabber.startLive();
//>>

		ResizeLiveVideo();

		TryAllocateBuffers( m_bufferMemoryMiB );
	}	
}

void CHighSpeedCaptureDlg::sinkConnected( DShowLib::FrameQueueSink& /*sink*/, const DShowLib::FrameTypeInfo& /*frameType*/ )
{	
}

void CHighSpeedCaptureDlg::sinkDisconnected( DShowLib::FrameQueueSink& /*sink*/ )
{
}

//<<framesQueued
void CHighSpeedCaptureDlg::framesQueued( DShowLib::FrameQueueSink& sink )
{
	std::vector<smart_ptr<DShowLib::FrameQueueBuffer>> buffers = sink.popAllOutputQueueBuffers();

	for( size_t i = 0; i < buffers.size(); ++i )
	{
		CString fileName;
		fileName.Format( TEXT("%s\\image_%d.bmp"), m_destinationFolder.GetString(), m_frameNumber++ );

		DShowLib::saveToFileBMP( *buffers[i], fileName );

		UpdateStats( sink, m_sinkStats, buffers.size() - i, 1 );

		sink.queueBuffer( buffers[i] );
	}

	UpdateStats( sink, m_sinkStats, 0, 0 );
}
//>>

void CHighSpeedCaptureDlg::OnBnClickedStartStop()
{
	if( m_pSink->getSinkMode() == DShowLib::GrabberSinkType::ePAUSE )
	{
		if( !::CreateDirectory( m_destinationFolder, NULL ) )
		{
		}		

		m_frameNumber = 0;

		m_pSink->setSinkMode( DShowLib::GrabberSinkType::eRUN );

		m_btnStartStop.SetWindowText( TEXT( "Stop" ) );

		m_btnDestinationBrowse.EnableWindow( FALSE );
		m_btnBufferMaximize.EnableWindow( FALSE );
	}
	else
	{
		m_pSink->setSinkMode( DShowLib::GrabberSinkType::ePAUSE );

		m_btnStartStop.SetWindowText( TEXT( "Start" ) );

		m_btnDestinationBrowse.EnableWindow( TRUE );
		m_btnBufferMaximize.EnableWindow( TRUE );
	}	
}

void CHighSpeedCaptureDlg::UpdateStatsUI( const SinkStats& stats )
{
	size_t numTotelFilledBuffers = stats.numFilledBuffers + stats.numProcessingBuffers;
	size_t numTotalBuffers = stats.numFreeBuffers + numTotelFilledBuffers;
	size_t freeMemory = stats.numFreeBuffers * stats.bufferSize;
	size_t filledMemory = numTotelFilledBuffers * stats.bufferSize;

	m_progressFreeBuffers.SetRange32( 0, (int)numTotalBuffers );
	m_progressFilledBuffers.SetRange32( 0, (int)numTotalBuffers );

	m_progressFreeBuffers.SetPos( (int)stats.numFreeBuffers );
	m_progressFilledBuffers.SetPos( (int)numTotelFilledBuffers );

	CString txt;
	txt.Format( TEXT( "%I64u/%I64u (%I64u MiB)" ), stats.numFreeBuffers, numTotalBuffers, freeMemory / (1024*1024) );
	m_lblFreeBuffers.SetWindowText( txt );

	txt.Format( TEXT( "%I64u/%I64u (%I64u MiB)" ), numTotelFilledBuffers, numTotalBuffers, filledMemory / (1024*1024) );
	m_lblFilledBuffers.SetWindowText( txt );

//<<imagestatistics
	DShowLib::DriverFrameDropInformation streamStats = {};
	m_Grabber.getDriverFrameDropInformation( streamStats );
	uint64_t driver_delivered = streamStats.FramesDelivered;
	uint64_t driver_dropped = streamStats.FramesDroppedDueToPacketLoss + streamStats.FramesDroppedDueToTransforms
							+ streamStats.FramesDroppedDueToApplicationQueue + streamStats.FramesDroppedDueUnspecified;

	DShowLib::FrameQueueSink::FrameCountInformation sinkStats = m_pSink->getFrameCountInfo();
	int64_t sink_copied = sinkStats.framesCopied;
	int64_t sink_dropped = sinkStats.framesDropped;	

	txt.Format( TEXT( "Driver: delivered %d, dropped %d - Sink: copied %d, dropped %d - Processed: %d" ), driver_delivered, driver_dropped, sink_copied, sink_dropped, stats.numProcessed );
	m_lblCaptureInfo.SetWindowText( txt );
//>>
}

void CHighSpeedCaptureDlg::UpdateStats( DShowLib::FrameQueueSink& sink, SinkStats& stats, size_t numProcessingBuffers, size_t addProcessed )
{
	stats.numFreeBuffers = sink.getInputQueueSize();
	stats.numFilledBuffers = sink.getOutputQueueSize();	
	stats.numProcessingBuffers = numProcessingBuffers;
	
	DShowLib::FrameTypeInfo ft;
	if( sink.getOutputFrameType( ft ) )
	{
		stats.bufferSize = ft.buffersize;
	}

	stats.numProcessed += addProcessed;

	// UpdateStats is called from the sink thread, we need to post a message to
	// the UI thread in order to avoid a deadlock on stopLive
	PostMessage( WM_UPDATE_STATS_UI );
}

void CHighSpeedCaptureDlg::OnClose()
{
	m_Grabber.stopLive();

	CDialogEx::OnClose();
}

LRESULT CHighSpeedCaptureDlg::OnUpdateStatsUI( WPARAM /*wParam*/, LPARAM /*lParam*/ )
{
	UpdateStatsUI( m_sinkStats );
	return 0;
}

BOOL CHighSpeedCaptureDlg::PreTranslateMessage( MSG* pMsg )
{
	if( pMsg->message == WM_KEYDOWN &&
		pMsg->wParam == VK_RETURN &&
		GetFocus()->GetDlgCtrlID() == IDC_EDIT_BUFFER_MEMORY )
	{
		UpdateData();
		TryAllocateBuffers( m_bufferMemoryMiB );

		return FALSE;
	}

	return __super::PreTranslateMessage( pMsg );
}


void CHighSpeedCaptureDlg::OnKillfocusEditBufferMemory()
{
	UpdateData();
	TryAllocateBuffers( m_bufferMemoryMiB );
}

void CHighSpeedCaptureDlg::TryAllocateBuffers( int bufferMemoryMiB )
{
	assert( m_pSink->getSinkMode() == DShowLib::GrabberSinkType::ePAUSE );

	DShowLib::FrameTypeInfo ft;
	if( !m_pSink->getOutputFrameType( ft ) )
	{
		return;
	}	

	size_t bufferMemoryBytes = bufferMemoryMiB * (size_t)(1024 * 1024);

	size_t totalBuffers = bufferMemoryBytes / ft.buffersize;
	size_t queuedBuffers = m_pSink->getInputQueueSize();

	HCURSOR hPrevCursor = SetCursor( ::LoadCursor( NULL, IDC_WAIT ) );

	if( totalBuffers > queuedBuffers )
	{
		m_pSink->allocAndQueueBuffers( totalBuffers - queuedBuffers );
	}
	else
	{
		DShowLib::tFrameQueueBufferList buffers = m_pSink->popAllInputQueueBuffers();

		// Shrinking the vector elements will free the image buffers
		buffers.resize( totalBuffers );

		m_pSink->queueBuffer( buffers );
	}

	SetCursor( hPrevCursor );

	UpdateStats( *m_pSink, m_sinkStats, 0, 0 );

	double bufferTime = totalBuffers / m_Grabber.getFPS();

	CString txt;
	txt.Format( TEXT("MiB - %.1f seconds"), bufferTime );
	SetDlgItemText( IDC_LABEL_BUFFERINFO, txt );
}

void CHighSpeedCaptureDlg::OnBnClickedBufferMaximize()
{
	MEMORYSTATUSEX memstat = { sizeof( memstat ) };
	::GlobalMemoryStatusEx( &memstat );

	size_t availablePhysicalMiB = memstat.ullAvailPhys / (1024 * 1024);

	const size_t KEEP_FREE_MIB = 1024;

	size_t extraMiB = (availablePhysicalMiB > KEEP_FREE_MIB) ? availablePhysicalMiB - KEEP_FREE_MIB : 0;

	m_bufferMemoryMiB += (int)extraMiB;
	UpdateData( false );

	TryAllocateBuffers( m_bufferMemoryMiB );
}


void CHighSpeedCaptureDlg::OnTimer( UINT_PTR nIDEvent )
{
	UpdateStatsUI( m_sinkStats );

	__super::OnTimer( nIDEvent );
}
